package cz.martindobias.aladin;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.*;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;

public class ImageListView extends View implements View.OnClickListener {
    private OverlayController overlay;
    private ImageListViewAdapter adapter = null;
    private int selectedImage = 0;
    private int initialImage = 0;
    private Bitmap image;
    private float scaleFactor = 1;
    private float scalePointX = 0f;
    private float scalePointY = 0f;
    private float translationX = 0f;
    private float translationY = 0f;
    private GestureDetector gestureDetector = null;
    private ScaleGestureDetector scaleGestureDetector = null;
    private boolean shallCenterOnRedraw = false;

    public ImageListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        int resource = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res/cz.martindobias.aladin", "overlay", 0);
        this.overlay = new OverlayController(this, resource);
        this.selectedImage = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/cz.martindobias.aladin", "startImage", 0);
        this.reset();

        ViewGroup container = this.overlay.getContainer();
        container.findViewById(R.id.zoomDown).setOnClickListener(this);
        container.findViewById(R.id.zoomUp).setOnClickListener(this);
        container.findViewById(R.id.zoomPage).setOnClickListener(this);
        container.findViewById(R.id.listDown).setOnClickListener(this);
        container.findViewById(R.id.listUp).setOnClickListener(this);

        this.gestureDetector = new GestureDetector(context, new BasicListener());
        this.scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.gestureDetector.onTouchEvent(event);
        this.scaleGestureDetector.onTouchEvent(event);
        this.overlay.setVisible(true);
        return true;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        this.overlay.setVisible(false);
    }

    public void setAdapter(ImageListViewAdapter adapter) {
        this.adapter = adapter;
        this.adapter.setOnChangeListener(new ImageListViewAdapter.OnChangeListener() {
            public void onChange(int index) {
                if(ImageListView.this.adapter != null && ImageListView.this.adapter.getImageCount() > 0
                        && (ImageListView.this.selectedImage == index || index == -1)) {
                    ImageListView.this.image = ImageListView.this.adapter.getBitmap(ImageListView.this.selectedImage);
                    ImageListView.this.invalidate();
                }
            }
        });
        this.reset();
    }

    private void reset() {
        if(this.initialImage > -1 || this.adapter == null) {
            this.selectedImage = this.initialImage;
        } else {
            this.selectedImage = this.adapter.getImageCount() - 1;
        }
        this.updateImage();

        this.scaleFactor = 1;
        this.shallCenterOnRedraw = true;

        this.updateOverlayStatus();
    }

    private void centerImage() {
        this.scaleFactor = Math.min(1, Math.min((float) this.getWidth() / this.image.getWidth(), (float) this.getHeight() / this.image.getHeight()));
        this.translationX = this.getWidth() / 2;
        this.translationY = this.getHeight() / 2;
    }

    private void updateOverlayStatus() {
        ViewGroup container = this.overlay.getContainer();
        container.findViewById(R.id.zoomDown).setEnabled(this.adapter != null && this.adapter.getImageCount() > 0 && this.scaleFactor > 0.1f);
        container.findViewById(R.id.zoomUp).setEnabled(this.adapter != null && this.adapter.getImageCount() > 0 && this.scaleFactor < 5f);
        container.findViewById(R.id.zoomPage).setEnabled(this.adapter != null && this.adapter.getImageCount() > 0);
        container.findViewById(R.id.listDown).setEnabled(this.adapter != null && this.selectedImage > 0);
        container.findViewById(R.id.listUp).setEnabled(this.adapter != null && this.selectedImage < this.adapter.getImageCount() - 1);
        if(this.adapter != null && this.adapter.getImageCount() > 0) {
            ((TextView) container.findViewById(R.id.textView)).setText((this.selectedImage + 1) + "/" + this.adapter.getImageCount());
        } else {
            ((TextView) container.findViewById(R.id.textView)).setText("");
        }
    }

    public void onClick(View view) {
        switch(view.getId()) {
            case R.id.zoomDown:
                this.scaleFactor /= 1.2f;
                break;
            case R.id.zoomUp:
                this.scaleFactor *= 1.2f;
                break;
            case R.id.zoomPage:
                this.scaleFactor = 1f;
                break;
            case R.id.listDown:
                this.selectedImage--;
                this.updateImage();
                break;
            case R.id.listUp:
                this.selectedImage++;
                this.updateImage();
                break;
            default:
                throw new IllegalArgumentException("Unknown view");
        }
        this.invalidate();
        this.updateOverlayStatus();
    }

    private void updateImage() {
        if(this.adapter != null && this.adapter.getImageCount() > 0) {
            this.selectedImage = Math.max(0, Math.min(this.selectedImage, this.adapter.getImageCount() - 1));
            this.image = this.adapter.getBitmap(this.selectedImage);
        } else {
            this.image = BitmapFactory.decodeResource(this.getResources(), R.drawable.no_picture);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if(this.image != null) {
            if(this.shallCenterOnRedraw) {
                this.centerImage();
                this.shallCenterOnRedraw = false;
            }

            this.scaleFactor = Math.max(0.1f, Math.min(this.scaleFactor, 5f));
            this.translationX = Math.max(-this.image.getWidth() / 2 * this.scaleFactor + 10,
                    Math.min(this.translationX, this.getWidth() + this.image.getWidth() / 2 * this.scaleFactor - 10));
            this.translationY = Math.max(-this.image.getHeight() / 2 * this.scaleFactor + 10,
                    Math.min(this.translationY, this.getHeight() + this.image.getHeight() / 2 * this.scaleFactor - 10));

            canvas.save();
            canvas.translate(this.translationX, this.translationY);
            canvas.scale(this.scaleFactor, this.scaleFactor, this.scalePointX, this.scalePointY);
            canvas.drawBitmap(this.image, -this.image.getWidth() / 2, -this.image.getHeight() / 2, null);
            canvas.restore();
        }
    }

    public void setInitialImage(int image) {
        this.initialImage = image;
    }

    private class BasicListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if(!ImageListView.this.scaleGestureDetector.isInProgress()) {
                ImageListView.this.translationX -= distanceX;
                ImageListView.this.translationY -= distanceY;
                ImageListView.this.invalidate();
            }
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            ImageListView.this.scaleFactor = 1;
            ImageListView.this.invalidate();
            ImageListView.this.updateOverlayStatus();
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if(Math.abs(velocityX) > Math.abs(velocityY) && Math.abs(velocityX) > 400f) {
                float rollbackX = e2.getX() - e1.getX();
                float rollbackY = e2.getY() - e1.getY();

                Animation animation = null;
                if(velocityX > 0 && ImageListView.this.adapter != null && ImageListView.this.selectedImage > 0) {
                    animation = AnimationUtils.loadAnimation(ImageListView.this.getContext(), R.anim.slideoutright);
                    animation.setAnimationListener(new AnimationEndListener(new ImageSwitch(ImageSwitch.SLIDE_IN_LEFT, ImageListView.this.selectedImage - 1, rollbackX, rollbackY)));
                }
                if(velocityX < 0 && ImageListView.this.adapter != null && ImageListView.this.selectedImage < ImageListView.this.adapter.getImageCount() - 1) {
                    animation = AnimationUtils.loadAnimation(ImageListView.this.getContext(), R.anim.slideoutleft);
                    animation.setAnimationListener(new AnimationEndListener(new ImageSwitch(ImageSwitch.SLIDE_IN_RIGHT, ImageListView.this.selectedImage + 1, rollbackX, rollbackY)));
                }
                if(animation != null) {
                    ImageListView.this.startAnimation(animation);
                    return true;
                }
            }
            return false;
        }
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if(detector.getCurrentSpan() > 10f) {
                ImageListView.this.scaleFactor *= detector.getScaleFactor();
                ImageListView.this.scalePointX = detector.getFocusX() - ImageListView.this.translationX;
                ImageListView.this.scalePointY = detector.getFocusY() - ImageListView.this.translationY;
                ImageListView.this.invalidate();
                ImageListView.this.updateOverlayStatus();
            }
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            ImageListView.this.scalePointX = 0;
            ImageListView.this.scalePointY = 0;
            ImageListView.this.invalidate();
        }
    }

    private class AnimationEndListener implements Animation.AnimationListener {
        private Runnable runnable;

        private AnimationEndListener(Runnable runnable) {
            this.runnable = runnable;
        }

        public void onAnimationStart(Animation animation) {
        }

        public void onAnimationEnd(Animation animation) {
            ImageListView.this.post(this.runnable);
        }

        public void onAnimationRepeat(Animation animation) {
        }
    }

    private class ImageSwitch implements Runnable {
        public static final int SLIDE_IN_RIGHT = 0;
        public static final int SLIDE_IN_LEFT = 1;

        private int style;
        private int imageIndex;
        private float rollbackX;
        private float rollbackY;

        public ImageSwitch(int style, int imageIndex, float rollbackX, float rollbackY) {
            this.style = style;
            this.imageIndex = imageIndex;
            this.rollbackX = rollbackX;
            this.rollbackY = rollbackY;
        }

        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p/>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see Thread#run()
         */
        public void run() {
            ImageListView.this.selectedImage = this.imageIndex;
            ImageListView.this.updateImage();
            ImageListView.this.translationX -= rollbackX;
            ImageListView.this.translationY -= rollbackY;
            ImageListView.this.updateOverlayStatus();

            Animation animation;
            switch(this.style) {
                case SLIDE_IN_RIGHT:
                    animation = AnimationUtils.loadAnimation(ImageListView.this.getContext(), R.anim.slideinright);
                    break;
                case SLIDE_IN_LEFT:
                    animation = AnimationUtils.loadAnimation(ImageListView.this.getContext(), R.anim.slideinleft);
                    break;
                default:
                    throw new IllegalArgumentException("Uknown switch style");
            }
            ImageListView.this.startAnimation(animation);
        }
    }
}
